package logic.algorithm.aco;

import java.util.ArrayList;
import javax.swing.JFrame;
import logic.objects.Connection;
import logic.objects.Node;
import logic.objects.Problem;
import logic.service.PheromonService;

public class AntSystem extends AcoMetaHeuristic {

	public AntSystem(Problem problem, JFrame frame) {
		super(problem, frame);
	}

	protected double beta;
	protected double alpha;
	protected double roh;

	@Override
	protected void initializeAnts() {
		ants = new ArrayList<Ant>();
		for (int i = 0; i < problem.getNodes().size(); i++) {
			ants.add(new Ant(problem));
		}
	}

	@Override
	protected void initializeParameters() {
		beta = 5.0;
		alpha = 1.0;
		roh = 0.5;
		tau0 = problem.getNodes().size()
				/ PheromonService.getNearestNeighborShortedTourValue(problem);
	}

	@Override
	protected void makeDecision(int k, int step) {
		ants.get(k).setTourInstanze(step, chooseNewNode(k, step));
	}

	public int chooseNewNode(int k, int step) {
		Double randomNumber = Math.random();
		Double pheromonCounter = 0.0;
		Integer choice = null;
		for (Node node : problem.getNodes()) {
			if (!(ants.get(k).getVisited(node.getId()))
					&& (ants.get(k).getTourInstanze(step - 1) != node.getId())) {
				Connection con = problem.getConnections().get(
						ants.get(k).getTourInstanze(step - 1), node.getId());
				if (randomNumber > pheromonCounter) {
					choice = problem.getNodes().indexOf(node);
				} else
					return choice;
				pheromonCounter += getProbability(con, k, step);
			}
		}
		if (choice == null) {
			throw (new NoNodeChoosenException());
		}
		return choice;
	}

	private Double getSingleProbabilityValue(Connection con) {
		Double pheromon = con.getPheromon();
		Double heuristicValue = con.getHeuristicalValue();
		Double total = Math.pow(pheromon, alpha)
				* Math.pow(heuristicValue, beta);
		// Double total = Math.pow(pheromon, ACOAlgorithm.alpha);
		return total;
	}

	private Double getTotalProbability(int k, int step) {
		Double total = 0.0;
		for (Node node : problem.getNodes()) {
			if (!ants.get(k).getVisited(node.getId())) {
				Connection con = problem.getConnections().get(
						ants.get(k).getTourInstanze(step - 1), node.getId());
				if (con != null && con.getLength() != null) {

					total += getSingleProbabilityValue(con);
				}
			}
		}
		return total;
	}

	private Double getProbability(Connection con, int k, int step) {
		return getSingleProbabilityValue(con) / getTotalProbability(k, step);
	}

	@Override
	protected void updatePheromon() {
		for (int i = 0; i < problem.getConnections().getSizeX(); i++) {
			for (int j = 0; j < problem.getConnections().getSizeY(); j++) {
				if (i != j) {
					Connection con = problem.getConnections().get(i, j);
					con.setPheromon(con.getPheromon() * (1.0 - roh));
				}
			}
		}
		for (Ant ant : ants) {
			updatePheromonByAnt(ant, 1.0);
		}
		problem.getConnections().updateMaxPheromon();
	}

	protected void updatePheromonByAnt(Ant ant, double amount) {
		Connection con;
		for (int i = 0; i < problem.getNodes().size() - 1; i++) {
			con = problem.getConnections().get(ant.getTourInstanze(i),
					ant.getTourInstanze(i + 1));
			con.setPheromon(con.getPheromon() + amount / ant.getTourLength());
		}
		con = problem.getConnections().get(
				ant.getTourInstanze(problem.getNodes().size() - 1),
				ant.getTourInstanze(0));
		con.setPheromon(con.getPheromon() + amount / ant.getTourLength());
	}

	protected void updatePheromonByBestSoFarSolution(double amount) {
		Connection con;
		ArrayList<Node> nodes = getBestTourSoFar();
		for (int i = 0; i < problem.getNodes().size() - 1; i++) {
			con = problem.getConnections().get(nodes.get(i).getId(),
					nodes.get(i + 1).getId());
			con.setPheromon(con.getPheromon() + amount
					/ getBestTourSoFarValue());
		}
		con = problem.getConnections().get(
				nodes.get(problem.getNodes().size() - 1).getId(),
				nodes.get(0).getId());
		con.setPheromon(con.getPheromon() + amount / getBestTourSoFarValue());
	}

	@Override
	protected boolean terminate() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public double getBestSolutionsCost() {
		double length = Double.MAX_VALUE;
		for (Ant ant : ants) {
			if (length > ant.getTourLength()) {
				length = ant.getTourLength();
			}
		}
		return length;
	}

	@Override
	public ArrayList<Node> getBestSolution() {
		int minIndex = 0;
		double length = Double.MAX_VALUE;
		for (int i = 0; i < ants.size(); i++) {
			if (length > ants.get(i).getTourLength()) {
				length = ants.get(i).getTourLength();
				minIndex = i;
			}
		}
		ArrayList<Node> tour = new ArrayList<Node>();
		for (int j = 0; j < problem.getNodes().size(); j++) {
			tour.add(problem.getNodes().get(
					ants.get(minIndex).getTourInstanze(j)));

		}
		return tour;

	}

	@Override
	protected void initializeStatistics() {
		for (int i = 0; i < problem.getConnections().getSizeX(); i++) {
			for (int j = 0; j < problem.getConnections().getSizeY(); j++) {
				if (i != j) {
					Double length = problem.getConnections().get(i, j)
							.getLength();
					if (length != null && length > 0.0) {
						problem.getConnections().get(i, j)
								.setHeuristicalValue(1.0 / length);
					} else {
						problem.getConnections().get(i, j)
								.setHeuristicalValue(0.0);
					}
				}
			}
		}
	}

	@Override
	protected void daemonAction() {
		// do nothing

	}
}
